Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reject future blocks #492

Merged
merged 11 commits into from
Jun 24, 2020
Merged

Conversation

teor2345
Copy link
Contributor

@teor2345 teor2345 commented Jun 16, 2020

Reject blocks that are more than 2 hours in the future, according to the node's clock.

I'll be able to complete this PR, once we've finished the async AddBlock() changes in PR #491.

Edit: PR #491 wasn't actually needed.

@teor2345 teor2345 added A-dependencies Area: Dependency file updates Poll::Ready A-consensus Area: Consensus rule updates A-rust Area: Updates to Rust code labels Jun 16, 2020
@teor2345 teor2345 added this to the Validate transactions. milestone Jun 16, 2020
@teor2345 teor2345 requested a review from a team June 16, 2020 13:38
@teor2345 teor2345 self-assigned this Jun 16, 2020
@teor2345 teor2345 mentioned this pull request Jun 16, 2020
16 tasks
@codecov
Copy link

codecov bot commented Jun 16, 2020

Codecov Report

Merging #492 into main will increase coverage by 0.37%.
The diff coverage is 48.28%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #492      +/-   ##
==========================================
+ Coverage   53.05%   53.42%   +0.37%     
==========================================
  Files          68       80      +12     
  Lines        3212     3942     +730     
==========================================
+ Hits         1704     2106     +402     
- Misses       1508     1836     +328     
Impacted Files Coverage Δ
tower-batch/src/error.rs 0.00% <0.00%> (ø)
tower-batch/src/layer.rs 0.00% <0.00%> (ø)
zebra-chain/src/keys/sapling.rs 85.30% <ø> (ø)
zebra-chain/src/lib.rs 50.00% <ø> (ø)
zebra-chain/src/merkle_tree.rs 0.00% <ø> (ø)
zebra-chain/src/note_commitment_tree.rs 0.00% <ø> (ø)
zebra-chain/src/serde_helpers.rs 0.00% <0.00%> (ø)
zebra-chain/src/transaction.rs 16.66% <ø> (+16.66%) ⬆️
zebra-chain/src/transaction/transparent.rs 100.00% <ø> (ø)
zebra-network/src/peer/connection.rs 0.00% <0.00%> (ø)
... and 75 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6771889...20dacb9. Read the comment docs.

@teor2345
Copy link
Contributor Author

In this case, the time check is a quick check, so we can just do it in call(), and if it fails, return async { Err() } as the Future.

But we'll still need PR #491 to do expensive checks in the async block.

hdevalence
hdevalence previously approved these changes Jun 16, 2020
Copy link
Contributor

@hdevalence hdevalence left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

dconnolly
dconnolly previously approved these changes Jun 16, 2020
Copy link
Contributor

@dconnolly dconnolly left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Writing a test to cover this seems annoying to set up so I'm approving without a test, but we may want to create an issue to make one and make it open to contributors / good-first-issue.

/// Returns true if the block header time is less than or equal to
/// 2 hours in the future, according to the node's local clock.
///
/// This is a non-deterministic rule, as clocks vary over time, and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

☝️

/// This is a non-deterministic rule, as clocks vary over time, and
/// between different nodes.
///
/// "In addition, a full validator MUST NOT accept blocks with nTime
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍👍

@teor2345
Copy link
Contributor Author

Writing a test to cover this seems annoying to set up so I'm approving without a test, but we may want to create an issue to make one and make it open to contributors / good-first-issue.

I think a test for "2 hours in the future" should be reasonably easy, let me see if I can set one up.

It will be harder to test overflow, I might need to refactor to do that.

@teor2345 teor2345 dismissed stale reviews from dconnolly and hdevalence via aa98871 June 17, 2020 11:25
@teor2345 teor2345 force-pushed the reject-future-blocks branch from 9957d0c to aa98871 Compare June 17, 2020 11:25
@teor2345 teor2345 changed the title WIP: Reject future blocks Reject future blocks Jun 17, 2020
@teor2345
Copy link
Contributor Author

I rebased this branch to handle the conflicts with the comments I cherry-picked from PR #491.

Writing a test to cover this seems annoying to set up so I'm approving without a test, but we may want to create an issue to make one and make it open to contributors / good-first-issue.

I think a test for "2 hours in the future" should be reasonably easy, let me see if I can set one up.

It will be harder to test overflow, I might need to refactor to do that.

I tested:

  • 2 hours in the future,
  • 2 hours and 1 second in the future,
  • a bunch of nearby times,
  • an old block, and
  • a bunch of significant time constants.

Utc.timestamp() panics when the timestamp is out of range. Its valid range is smaller than i64, so I don't think overflow is possible. (But if it is, the check handles it correctly, by returning an error.)

The Utc.timestamp() valid range is larger than the Zcash time range (u32), so all Zcash times are valid in Rust. I've made a note in #477 to check for overflows when adding times from multiple blocks.

@teor2345 teor2345 requested review from hdevalence and dconnolly June 18, 2020 07:54
@teor2345 teor2345 marked this pull request as draft June 18, 2020 11:33
@teor2345
Copy link
Contributor Author

I've set this PR to draft, because I need to fix an issue with early state commits, as discussed here:
#502 (comment)

I should also add some tests, to make sure that the state isn't changed on verification errors.

Comment on lines 63 to 82
//
// `tower::Buffer` expects exactly one `call` for each
// `poll_ready`. So we unconditionally create the AddBlock
// Future using `state_service.call`. If verification fails,
// we return an error, and implicitly cancel the future.
let add_block = self.state_service.call(zebra_state::Request::AddBlock {
block: block.clone(),
});

async move {
// If verification fails, return an error result.
// The AddBlock Future is implicitly cancelled by the
// error return in `?`.

// Since errors cause an early exit, try to do the
// quick checks first.
block::node_time_check(block)?;

// Verification was successful.
// Add the block to the state by awaiting the AddBlock
// Future, and return its result.
match add_block.await? {
zebra_state::Response::Added { hash } => Ok(hash),
_ => Err("adding block to zebra-state failed".into()),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed in #502, we can't conditionally await an AddBlock future. Because the state service can commit changes in call(), or in the Future's async block (that is, in await).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 589f1ce.

zebra-chain/src/block.rs Show resolved Hide resolved
///
/// [7.5]: https://zips.z.cash/protocol/protocol.pdf#blockheader
pub(super) fn node_time_check(block: Arc<Block>) -> Result<(), Error> {
node_time_check_helper(block.header.time, Utc::now())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIOLI: I don't think this benefits from being broken out into a separate function, I'd prefer to start the fn with

let block_header_time = block.header.time;
let now = Utc::now();

// The rest of node_time_check_helper's function body...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, it made more sense before I split out the node_time_check_helper() for testing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 347f5ba.

@teor2345 teor2345 marked this pull request as ready for review June 22, 2020 00:14
@teor2345 teor2345 requested a review from yaahc June 22, 2020 00:14
@teor2345
Copy link
Contributor Author

I think we can let GitHub squash and merge this PR.

(I could do a manual squash into a time check commit, a BlockVerifier refactor commit, and other changes. Happy to do that if people think it's worth the effort.)

dconnolly
dconnolly previously approved these changes Jun 22, 2020
Copy link
Contributor

@dconnolly dconnolly left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these tests fill me with joy ☺️

zebra-consensus/src/verify.rs Show resolved Hide resolved
zebra-consensus/src/verify.rs Show resolved Hide resolved
// TODO(teor || jlusby): check error string
_ => true,
},
// TODO(teor || jlusby): check error string
Copy link
Contributor

@yaahc yaahc Jun 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't default to doing string comparisons, if there's ever a specific error that we need to check for that is being constructed from str -> Box<dyn Error> we should instead change the error to be a proper error type that we can downcast to, using string's content as a proxy for a typed error is a recipe for disaster.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll fix these comments, possibly in a future PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #538 in 5f96b32 for verify/block

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #538 in 609de23 for checkpoint

zebra-consensus/src/verify.rs Show resolved Hide resolved
hdevalence
hdevalence previously approved these changes Jun 22, 2020
Copy link
Contributor

@hdevalence hdevalence left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIOLI: I think the BlockVerifier belongs in the block submodule, and generally I think we should keep all of the contents of the code in the leaves of the module tree and then re-export as necessary.

@dconnolly
Copy link
Contributor

Looks like this needs a rebase but LGTM! 💃

teor2345 added 11 commits June 24, 2020 16:48
We don't need to check transaction sizes yet, because we aren't
parsing or generating transactions outside of blocks.

Part of ZcashFoundation#483.
Add a function that checks the block header miner times against the
node's local clock.

(We'll use this function in the next commit.)

Part of ZcashFoundation#477.
Use MAX constants for the block header version and time arbitrary test
ranges. Reduces the block header time arbitrary test range from 2**32 to
2**32-1 (u32::MAX). (2**32 is an invalid time value, which gets
truncated to 0 during serialization.)

Also add some comments about DateTime conversions.

Part of ZcashFoundation#477.
We don't want to call the state's AddBlock until we know the block is
valid. This avoids subtle bugs if the state is modified in call().

(in_memory currently modifies the state in call(), on_disk modifies the
state in the async block.)

Part of ZcashFoundation#477.
node_time_check() is a small function, so we inline it into its callers.
(And then rename node_time_check_helper() to node_time_check().)

Part of ZcashFoundation#477.
@teor2345 teor2345 dismissed stale reviews from hdevalence and dconnolly via 20dacb9 June 24, 2020 07:01
@teor2345 teor2345 force-pushed the reject-future-blocks branch from 153a968 to 20dacb9 Compare June 24, 2020 07:01
@teor2345
Copy link
Contributor Author

I rebased and made the following changes:

  • zebra-test module name change
  • color-eyre/eyre module import changes

I'll merge after CI passes, I don't think the differences are large enough for a review.

@teor2345 teor2345 merged commit a9efb87 into ZcashFoundation:main Jun 24, 2020
@teor2345
Copy link
Contributor Author

The checks passed, and the changes are minor, so I merged this PR.

@teor2345 teor2345 mentioned this pull request Jun 24, 2020
@teor2345
Copy link
Contributor Author

TIOLI: I think the BlockVerifier belongs in the block submodule, and generally I think we should keep all of the contents of the code in the leaves of the module tree and then re-export as necessary.

Fixed in #538 in e2999fc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-consensus Area: Consensus rule updates A-dependencies Area: Dependency file updates A-rust Area: Updates to Rust code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants